#include <stdint.h>
#include <stdfunc.h>
#include <memory.h>
#include <task.h>
#include <cpuid.h>
#include <text_video.h>
#include <acpi.h>

static uint8_t acpi_checksum(const uint32_t start, const uint32_t count)
{   
    uint32_t i;
    uint8_t chksum;
    
    chksum = 0;
    for(i = 0; i < count; i++)
    {
        chksum = (uint8_t)(chksum + *(uint8_t*)(start + i));
    }
    
    return chksum;
}

static void parse_apic(const uint32_t location)
{
    uint32_t sub_location;
    uint8_t sub_type, sub_length;

    acpi_lapic_count = 0;
    acpi_ioapic_count = 0;
    acpi_iso_count = 0;
    acpi_nmi_count = 0;
    acpi_lapic_nmi_count = 0;
    acpi_lapic_ao_count = 0;
    acpi_iosapic_count = 0;

    copy_memory((uint8_t*)&acpi_apic, (uint8_t*)(location), min(44, *(uint32_t*)(location + sizeof(uint32_t))));
    
    printf(" : APIC:\n");
    printf("   : length = %u\n", acpi_apic.length);
    printf("   : LAPIC address = %x\n", acpi_apic.lapic_address);
    
    printf("   : Structures:\n");
    sub_location = location + 44;
    
    while(sub_location < (location + acpi_apic.length))
    {
        sub_type = *(uint8_t*)(sub_location + 0);
        sub_length = *(uint8_t*)(sub_location + 1);
        
        printf("     : type #%u:\n", sub_type);
        
        switch(sub_type)
        {
            case 0:
                copy_memory((uint8_t*)&acpi_apic_lapic[acpi_lapic_count++], (uint8_t*)(sub_location), 8);
                break;
            
            case 1:
                copy_memory((uint8_t*)&acpi_apic_ioapic[acpi_ioapic_count++], (uint8_t*)(sub_location), 12);
                break;
                
            case 2:
                copy_memory((uint8_t*)&acpi_apic_iso[acpi_iso_count++], (uint8_t*)(sub_location), 10);
                break;   
                
            case 3:
                copy_memory((uint8_t*)&acpi_apic_nmi[acpi_nmi_count++], (uint8_t*)(sub_location), 8);
                break;
                
            case 4:
                copy_memory((uint8_t*)&acpi_apic_lapic_nmi[acpi_lapic_nmi_count++], (uint8_t*)(sub_location), 6);
                break;    
                        
            case 5:
                copy_memory((uint8_t*)&acpi_apic_lapic_ao[acpi_lapic_ao_count++], (uint8_t*)(sub_location), 12);
                break;   
                           
            case 6:
                copy_memory((uint8_t*)&acpi_apic_iosapic[acpi_iosapic_count++], (uint8_t*)(sub_location), 16);
                break;
                
                    
            default:
                break;
        }
        
        sub_location += sub_length;
    }
}

static void parse_facp(const uint32_t location)
{
    uint32_t usable_length;
    
    /* we shouldn't be parsing the "extended" area as this is a 32-bit OS */    
    usable_length = min(132, *(uint32_t*)(location + sizeof(uint32_t)));
    
    copy_memory((uint8_t*)&acpi_facp, (uint8_t*)(location), usable_length);
    
    acpi_facp_oem_id = memalloc(7, 0);
    copy_memory(acpi_facp_oem_id, acpi_facp.oem_id, 6);
    acpi_facp_oem_id[6] = 0;
}

static void parse_tables(const uint32_t * const table, const uint32_t entries)
{
    uint32_t i, x;
    
    for(i = 0; i < entries; i++)
    {
        for(x = 0; x < 25; x++)
        {
            /* OEM-specific tables use the signature of OEM[x] and we should only scan for "OEM" */
            if(x == 5)
            {
                if(!compare_memory((uint8_t*)(table[i]), (uint8_t*)(header_signatures[x]), 3))
                {break;}
            }
            else
            {
                if(!compare_memory((uint8_t*)(table[i]), (uint8_t*)(header_signatures[x]), 4))
                {break;}
            }
        }
        
        /* if we've exhausted all of the possible tables, we've encountered an error */
        if(x >= 25)
        {
            printf("ACPI: invalid table, aborting...\n");
            return;
        }
        
        switch(x)
        {
            case 0:
                parse_apic(table[i]);
                break;
        
            case 3:
                parse_facp(table[i]);
                break;
                
            default:
                printf("ACPI: unknown table (%u)\n", x);
                break;
        }
    }
}

/* Root System Description Table (RSDT) */
static void parse_rsdt(const uint32_t location)
{
    if(compare_memory((uint8_t*)location, (uint8_t*)"RSDT ", 4))
    {printf("ACPI: RSDT signature is incorrect!\n");return;}

    copy_memory((uint8_t*)&acpi_rsdt, (uint8_t*)location, sizeof(acpi_rsdt));

    if(acpi_checksum(location, acpi_rsdt.length))
    {printf("ACPI: RSDT checksum is incorrect!\n");return;}
    
    acpi_present = true;
    
    /* number of tables is equal to the table length minus the header (36 bytes) and divided by the size of a 32-bit address (4) */
    parse_tables((uint32_t*)(location + 36), ((acpi_rsdt.length - 36) / 4));
}

/* Root System Description Pointer (RSDP) */
static void parse_rsdp(const uint32_t location)
{
    if(acpi_checksum(location, 20))
    {printf("ACPI: RSDP checksum is incorrect!\n");return;}
    
    copy_memory((uint8_t*)&acpi_rsdp, (uint8_t*)location, sizeof(acpi_rsdp));
    
    /* revisions start at 0 but translation is +1 */
    printf("ACPI: version %u\n", acpi_rsdp.revision+1);
    
    parse_rsdt(acpi_rsdp.rsdt_address);
}

static void cleanup_task(void)
{
    kill_task(active_task_id);
}

void parse_acpi(void)
{
    uint32_t i, ebda;
    
    acpi_parsing_completed = false;
    acpi_present = false;
    
    for(i = 0xE0000; i < 0x100000; i += 16)
    {
        if(!compare_memory((uint8_t*)i, (uint8_t*)"RSD PTR ", 8))
        {
            printf("ACPI: RSDP found at %x\n", i);
            parse_rsdp(i);
            acpi_parsing_completed = true;
            cleanup_task();
        }
        GUTS();
    }
    
    /* get the Extended BIOS Data Area location which is located at 0x40:0x0E */
    ebda = (uint32_t)(*(uint16_t*)(0x40E) & 0xFFFF);

    for(i = ebda; i < (ebda + 1024); i += 16)
    {
        if(!compare_memory((uint8_t*)i, (uint8_t*)"RSD PTR ", 8))
        {
            printf("ACPI: RSDP found at %x\n", i);
            parse_rsdp(i);
            acpi_parsing_completed = true;
            cleanup_task();
        }
        GUTS();
    }
    
    acpi_parsing_completed = true;
    cleanup_task();
}
